package com.wanjia.estate.aop;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.BooleanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONException;
import com.wanjia.estate.properties.MockProxyServerProperties;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

@Aspect
@Component
@Slf4j
public class MockProxyServer {

    @Setter(onMethod_ = @Autowired)
    private MockProxyServerProperties properties;

    @Around("execution(* com.wanjia.estate.controller..*(..)) || execution(* com.wanjia.estate.service..*(..))")
    public Object pointCut(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        if (!BooleanUtil.isTrue(properties.getEnable())) {
            return joinPoint.proceed(args);
        }

        log.debug("MockProxyServer works");
        Signature signature = joinPoint.getSignature();
        List<MockProxyServerProperties.Config> configs = Optional.of(properties)
                .map(MockProxyServerProperties::getConfigs)
                .orElseGet(ArrayList::new);
        for (MockProxyServerProperties.Config config : configs) {
            List<Class<?>> parameters = config.getParameters();
            Class<?> target = config.getTarget();
            String method = config.getMethod();
            Class<?> returnType = config.getReturnType();
            if (Objects.equals(target, signature.getDeclaringType()) &&
                    Objects.equals(method, signature.getName()) &&
                    checkArgs(args, parameters) && checkReturnType(target, method, parameters, returnType)) {
                log.debug("MockProxyServer matches");
                return getReturnData(config);
            }
        }

        log.debug("MockProxyServer not matches");
        return joinPoint.proceed(args);
    }

    private Object getReturnData(MockProxyServerProperties.Config config) {
        switch (config.getType()) {
            case JSON_DATA:
                return parseJson(config.getJson(), config.getReturnType());
            case JSON_FILE:
                try (InputStream in = config.getFile().getInputStream()) {
                    String json = IoUtil.read(in, StandardCharsets.UTF_8);
                    return parseJson(json, config.getReturnType());
                } catch (IOException e) {
                    log.error("文件读取异常", e);
                }
            default:
                return null;
        }
    }

    private Object parseJson(String json, Class<?> c) {
        if (StrUtil.isBlank(json)) {
            return null;
        }

        if (json.startsWith("[")) {
            return JSON.parseArray(json, c);
        }

        if (json.startsWith("{")) {
            return JSON.parseObject(json, c);
        }

        throw new JSONException("未知的JSON格式: " + json);
    }

    private boolean checkArgs(Object[] args, List<Class<?>> parameters) {
        int size1 = ArrayUtil.isEmpty(args) ? 0 : args.length;
        int size2 = CollUtil.isEmpty(parameters) ? 0 : parameters.size();
        if (size1 != size2) {
            return false;
        }

        for (int i = 0; i < size1; i++) {
            Object arg = args[i];
            Class<?> c = parameters.get(i);
            if (arg != null && !c.isInstance(arg)) {
                return false;
            }
        }
        return true;
    }

    private boolean checkReturnType(Class<?> target, String methodName, List<Class<?>> parameters, Class<?> returnType) {
        try {
            Method method = target.getDeclaredMethod(methodName, parameters.toArray(new Class<?>[]{}));
            return returnType.isAssignableFrom(method.getReturnType());
        } catch (NoSuchMethodException e) {
            log.error("方法匹配失败", e);
            return false;
        }
    }
}
